package com.pan.uml.impl;


import com.pan.entity.UMLInformation;
import com.pan.uml.ParseUML;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PanParseUML implements ParseUML {

    private static final int PUBLIC_SIGN = 0b0001;
    private static final int PRIVATE_SIGN = 0b0010;
    private static final int PROTECTED_SIGN = 0b0100;
    private Pattern lastPattern = Pattern.compile("(?<=\\.)[^\\.]+$");

    public String toLastName(String fullName) {
        Matcher matcher = lastPattern.matcher(fullName);
        if (matcher.find()) {
            return matcher.group();
        }
        return fullName;
    }

    private String toSign(int mod) {
        if ((PUBLIC_SIGN & mod) != 0) return "+";
        if ((PRIVATE_SIGN & mod) != 0) return "-";
        if ((PROTECTED_SIGN & mod) != 0 || mod == 0) return "#";
        else return Modifier.toString(mod);
//        throw new RuntimeException("不认识该标志 Don't recognize the sign " + mod);
    }

    public String toFieldUMLString(Field field) {
        return toFieldUMLString(field, true);
    }

    public String toFieldUMLString(Field field, boolean isFull) {
        StringJoiner sj = new StringJoiner(" ");
        sj.add(toSign(field.getModifiers()));
        sj.add(field.getName() + ":");
        String typeName = field.getType().getTypeName();
        sj.add(isFull ? typeName : toLastName(typeName));
        return sj.toString();
    }

    public String[] getFieldsUMLString(Field[] fields) {
        return getFieldsUMLString(fields, true);
    }

    public String[] getFieldsUMLString(Field[] fields, boolean isFull) {
        String[] fieldsString = new String[fields.length];
        for (int i = 0; i < fieldsString.length; i++) {
            fieldsString[i] = toFieldUMLString(fields[i], isFull);
        }
        return fieldsString;
    }

    public String toConstructorUMLString(Constructor constructor, boolean isFull) {
        StringJoiner sj = new StringJoiner(" ");
        sj.add(toSign(constructor.getModifiers()));
        Class[] typeParameters = constructor.getParameterTypes();
        StringJoiner paramStrJoiner = new StringJoiner(",");
        if (isFull) {
            for (Class typeParameter : typeParameters) {
                paramStrJoiner.add(typeParameter.getTypeName());
            }
        } else {
            for (Class typeParameter : typeParameters) {
                paramStrJoiner.add(toLastName(typeParameter.getTypeName()));
            }
        }
        sj.add((isFull ? constructor.getName() : toLastName(constructor.getName()))
                + "(" + paramStrJoiner.toString() + ")");
        return sj.toString();
    }

    public String toConstructorUMLString(Constructor constructor) {
        return toConstructorUMLString(constructor, true);
    }

    public String[] getConstructorsUMLString(Constructor[] constructors, boolean isFull) {
        String[] constructorsString = new String[constructors.length];
        for (int i = 0; i < constructorsString.length; i++) {
            constructorsString[i] = toConstructorUMLString(constructors[i], isFull);
        }
        return constructorsString;
    }

    public String[] getConstructorsUMLString(Constructor[] constructors) {
        return getConstructorsUMLString(constructors, true);
    }


    public String toMethodUMLString(Method method, boolean isFull) {
        StringJoiner sj = new StringJoiner(" ");
        sj.add(toSign(method.getModifiers()));
        String methodName = isFull ? method.getName() : toLastName(method.getName());
        Class<?>[] parameterTypes = method.getParameterTypes();
        StringJoiner paramsSj = new StringJoiner(",");
        if (isFull) {
            for (Class<?> parameterType : parameterTypes) {
                String ptn = parameterType.getTypeName();
                paramsSj.add(toLastName(ptn));
            }
        } else {
            for (Class<?> parameterType : parameterTypes) {
                paramsSj.add(parameterType.getTypeName());
            }
        }
        sj.add(methodName + "(" + paramsSj + "):");
        String returnTypeName = isFull ? method.getReturnType().getTypeName() : toLastName(method.getReturnType().getTypeName());
        sj.add(returnTypeName);
        return sj.toString();
    }

    public String[] getMethodsUMLString(Method[] methods, boolean isFull) {
        String[] methodsString = new String[methods.length];
        for (int i = 0; i < methodsString.length; i++) {
            methodsString[i] = toMethodUMLString(methods[i], isFull);
        }
        return methodsString;
    }

    public int maxLength(String[]... arr) {
        int max = 0;
        for (int j = 0; j < arr.length; j++) {
            for (int i = 0; i < arr[j].length; i++) {
                if (arr[j][i].length() > max) {
                    max = arr[j][i].length();
                }
            }
        }
        return max;
    }

    @Override
    public UMLInformation toUML(Class c, boolean isFull) {
        UMLInformation umlInformation = new UMLInformation();
        Field[] fields = c.getDeclaredFields();
        Constructor[] constructors = c.getConstructors();
        Method[] methods = c.getDeclaredMethods();
        String[] fieldsUMLString = getFieldsUMLString(fields, isFull);
        String[] constructorsUMLString = getConstructorsUMLString(constructors, isFull);
        String[] methodsUMLString = getMethodsUMLString(methods, isFull);
        if (c.isInterface()){
            umlInformation.setClassName("《Interface》&#xa;"+c.getName());
        }else {
            umlInformation.setClassName(c.getName());
        }
        umlInformation.setFields(fieldsUMLString);
        umlInformation.setConstructors(constructorsUMLString);
        umlInformation.setMethods(methodsUMLString);
        umlInformation.setXSize(maxLength(fieldsUMLString,constructorsUMLString,methodsUMLString));
        umlInformation.setYSize(methods.length+fields.length+constructors.length);
        return umlInformation;
    }

    @Override
    public UMLInformation toUML(Class c) {
        return toUML(c,true);
    }
}
